// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package runtime

import (
	"runtime/internal/sys"
	"unsafe"
)

const pageCachePages = 8 * unsafe.Sizeof(pageCache{}.cache)

// pageCache表示分配器可以不带锁地从中分配的页的per-p缓存。更具体地说，它表示内存中包含0个或更多可用
// 页的pageCachePages*pageSize块。
type pageCache struct {
	base  uintptr // 块的基址
	cache uint64  // 表示空闲页的64位位图（1表示空闲）
	scav  uint64  // 表示已清除页的64位位图（1表示已清除）
}

// 如果页面缓存有任何空闲页，则返回true，否则返回false 
// 否则返回true。
func (c *pageCache) empty() bool {
	return c.cache == 0
}

// alloc从页面缓存分配NPAGE，是分配的主条目
// 点。
// 
// 返回
// 分配区域中的基址和清除的内存量（字节）。
// 
// 失败时返回零的基址，在这种情况下，应忽略
// 清除的内存量。
func (c *pageCache) alloc(npages uintptr) (uintptr, uintptr) {
	if c.cache == 0 {
		return 0, 0
	}
	if npages == 1 {
		i := uintptr(sys.TrailingZeros64(c.cache))
		scav := (c.scav >> i) & 1
		c.cache &^= 1 << i // 设置位以标记正在使用
		c.scav &^= 1 << i  // 清除位以标记未屏蔽
		return c.base + i*pageSize, uintptr(scav) * pageSize
	}
	return c.allocN(npages)
}

// allocN是一个帮助程序，它尝试从缓存中分配相当于页数的npages 
// 页。它表示从
// 分配页面缓存的一般情况。
// 
// 返回
// 分配区域中的基址和清除内存量（字节）。
func (c *pageCache) allocN(npages uintptr) (uintptr, uintptr) {
	i := findBitRange64(c.cache, uint(npages))
	if i >= 64 {
		return 0, 0
	}
	mask := ((uint64(1) << npages) - 1) << i
	scav := sys.OnesCount64(c.scav & mask)
	c.cache &^= mask // 标记正在使用的位
	c.scav &^= mask  // 清除清除的位
	return c.base + uintptr(i*pageSize), uintptr(scav) * pageSize
}

// 清除将给定缓存中未分配的可用页清空
// 到s中。然后，它清除缓存，使得empty返回
// true。
// 
// p.mheapLock必须保持。必须在系统堆栈上运行，因为必须持有p.mheapLock。
// 
// go:systemstack 
func (c *pageCache) flush(p *pageAlloc) {
	assertLockHeld(p.mheapLock)

	if c.empty() {
		return
	}
	ci := chunkIndex(c.base)
	pi := chunkPageIndex(c.base)

	// 此方法很少被调用，所以只需执行
	// 通过逐个迭代每个位来执行更慢、更安全的操作即可。
	for i := uint(0); i < 64; i++ {
		if c.cache&(1<<i) != 0 {
			p.chunkOf(ci).free1(pi + i)
		}
		if c.scav&(1<<i) != 0 {
			p.chunkOf(ci).scavenged.setRange(pi+i, 1)
		}
	}
	// 因为这很像免费的，我们需要确保
	// 我们像免费一样更新searchAddr。
	if b := (offAddr{c.base}); b.lessThan(p.searchAddr) {
		p.searchAddr = b
	}
	p.update(c.base, pageCachePages, false, false)
	*c = pageCache{}
}

// AlloctCache获取一个与pageCachePages对齐的空闲页块，该块可能不连续，并返回一个拥有该块的pageCache结构。
// 
// p.mheapLock必须保持。必须在系统堆栈上运行，因为必须持有p.mheapLock。
// 
// go:systemstack 
func (p *pageAlloc) allocToCache() pageCache {
	assertLockHeld(p.mheapLock)

	// 如果searchAddr引用的区域的地址高于
	// 任何已知块，则我们知道内存不足。
	if chunkIndex(p.searchAddr.addr()) >= p.end {
		return pageCache{}
	}
	c := pageCache{}
	ci := chunkIndex(p.searchAddr.addr()) // 区块索引
	if p.summary[len(p.summary)-1][ci] != 0 {
		// 快速路径：在searchAddr地址或其附近有免费页面。
		chunk := p.chunkOf(ci)
		j, _ := chunk.find(1, chunkPageIndex(p.searchAddr.addr()))
		if j == ^uint(0) {
			throw("bad summary data")
		}
		c = pageCache{
			base:  chunkBase(ci) + alignDown(uintptr(j), 64)*pageSize,
			cache: ^chunk.pages64(j),
			scav:  chunk.scavenged.block64(j),
		}
	} else {
		// 慢路径：searchAddr地址在那里什么都没有，所以去查找
		// 慢路径的第一个免费页面。
		addr, _ := p.find(1)
		if addr == 0 {
			// 我们找不到足够的可用空间，因此将searchAddr标记为OoM 
			// 并返回一个空的页面缓存。
			p.searchAddr = maxSearchAddr
			return pageCache{}
		}
		ci := chunkIndex(addr)
		chunk := p.chunkOf(ci)
		c = pageCache{
			base:  alignDown(addr, 64*pageSize),
			cache: ^chunk.pages64(chunkPageIndex(addr)),
			scav:  chunk.scavenged.block64(chunkPageIndex(addr)),
		}
	}

	// 将位设置为已分配，并清除清除的位。
	p.allocRange(c.base, pageCachePages)

	// 作为分配更新，但请注意它不是连续的。
	p.update(c.base, pageCachePages, false, true)

	// 将搜索地址设置为缓存表示的最后一页。
	// 由于此块中的所有页面都将进入缓存，并且我们
	// 搜索了第一个可用页面，因此我们可以放心地从
	// 下一页开始。
	// 
	// 但是，不允许p.searchAddr指向未映射的堆内存
	// 除非它是maxSearchAddr，所以将其作为最后一页，而不是后面的页面。
	p.searchAddr = offAddr{c.base + pageSize*(pageCachePages-1)}
	return c
}
